package com.info.zhiduoduo.weixin.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.info.zhiduoduo.common.annotation.RepeatSubmitVerify;
import com.info.zhiduoduo.common.constants.StatusMsg;
import com.info.zhiduoduo.common.exception.ApiException;
import com.info.zhiduoduo.common.exception.SmartException;
import com.info.zhiduoduo.common.validator.IdempotentConstant;
import com.info.zhiduoduo.core.service.common.AuthService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.util.Assert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DigestUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author tianyonghong
 * @create 2022/1/12
 */
@Aspect
@Component
public class RepeatSubmitVerifyAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.info.zhiduoduo.common.annotation.RepeatSubmitVerify)")
    public void repeatSubmitVerify() {
    }

    /**
     * @param joinPoint
     * @return
     */
    @Around("repeatSubmitVerify()")
    public Object before(ProceedingJoinPoint joinPoint) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Assert.notNull(request, "request cannot be null.");

        //获取执行方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取防重复提交注解
        RepeatSubmitVerify annotation = method.getAnnotation(RepeatSubmitVerify.class);

        // token或请求地址作为存放cache的key值，生成redisKey和redisValue
        String token = request.getHeader(AuthService.AUTH_HEADER);
        if (StringUtils.isEmpty(token)) {
            token = request.getRequestURI();
        }

        String redisKey = IdempotentConstant.REPEAT_SUBMIT_PREFIX.concat(token).concat(getMethodSign(method, joinPoint.getArgs()));
        String redisValue = redisKey.concat(annotation.value()).concat("submit duplication");

        if (!redisTemplate.hasKey(redisKey)) {
            //设置防重复操作限时标记（前置通知）
            redisTemplate.opsForValue().set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS);
            try {
                //正常执行方法
                Object obj = joinPoint.proceed();

                //执行以后释放限时标记
                redisTemplate.delete(redisKey);

                return obj;
            } catch (SmartException e){
                throw e;
            } catch (Throwable throwable) {
                throw new RuntimeException(throwable);
            } finally {
                //确保方法执行异常实时释放限时标记(异常后置通知)
                redisTemplate.delete(redisKey);
            }
        } else {
            throw new SmartException(StatusMsg.StatusMsg_141);
        }
    }


    /**
     * 生成方法标记：采用数字签名算法SHA1对方法签名字符串加签
     *
     * @param method
     * @param args
     * @return
     */
    private String getMethodSign(Method method, Object... args) {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args) {
            sb.append(toString(arg));
        }
        return DigestUtils.sha1DigestAsHex(sb.toString());
    }

    private String toString(Object arg) {
        if (Objects.isNull(arg)) {
            return "null";
        }
        if (arg instanceof Number) {
            return arg.toString();
        }
        return JSONObject.toJSONString(arg);
    }
}
